package com.uyayo.system.monitor.interceptor;

import com.uyayo.framework.common.util.CodeUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.scheduling.annotation.Async;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Properties;

/** 
 * 
 * SQL执行拦截器。
 * 
 */
@Slf4j
@Intercepts( {@Signature(method = "prepare", type = StatementHandler.class, args = {Connection.class,Integer.class }) })
public class SqlExecuteInterceptor implements Interceptor {

	/**
     * 拦截后要执行的方法 
     */  
    public Object intercept(Invocation invocation) throws Throwable {  
       //对于StatementHandler其实只有两个实现类，一个是RoutingStatementHandler，另一个是抽象类BaseStatementHandler，  
       //BaseStatementHandler有三个子类，分别是SimpleStatementHandler，PreparedStatementHandler和CallableStatementHandler，  
       //SimpleStatementHandler是用于处理Statement的，PreparedStatementHandler是处理PreparedStatement的，而CallableStatementHandler是  
       //处理CallableStatement的。Mybatis在进行Sql语句处理的时候都是建立的RoutingStatementHandler，而在RoutingStatementHandler里面拥有一个  
       //StatementHandler类型的delegate属性，RoutingStatementHandler会依据Statement的不同建立对应的BaseStatementHandler，即SimpleStatementHandler、  
       //PreparedStatementHandler或CallableStatementHandler，在RoutingStatementHandler里面所有StatementHandler接口方法的实现都是调用的delegate对应的方法。  
       //我们在PageInterceptor类上已经用@Signature标记了该Interceptor只拦截StatementHandler接口的prepare方法，又因为Mybatis只有在建立RoutingStatementHandler的时候  
       //是通过Interceptor的plugin方法进行包裹的，所以我们这里拦截到的目标对象肯定是RoutingStatementHandler对象。  
       RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();  
       //通过反射获取到当前RoutingStatementHandler对象的delegate属性  
       StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler, "delegate");
       //获取到当前StatementHandler的 boundSql，这里不管是调用handler.getBoundSql()还是直接调用delegate.getBoundSql()结果是一样的，因为之前已经说过了  
       //RoutingStatementHandler实现的所有StatementHandler接口方法里面都是调用的delegate对应的方法。  
       BoundSql boundSql = delegate.getBoundSql();  

       //通过反射获取delegate父类BaseStatementHandler的mappedStatement属性
       MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement");
		//拦截到的prepare方法参数是一个Connection对象
		Connection connection = (Connection)invocation.getArgs()[0];

		Configuration configuration = mappedStatement.getConfiguration();
		Object returnValue;
		long start = System.currentTimeMillis();
		Date startTime = new Date();
		returnValue = invocation.proceed();
		Date costTime = new Date();
		long end = System.currentTimeMillis();
		long time = end - start;

		//if (paramsService.getParamByCode("SYS_SQL_MONITOR").equals("1")) {
			String sql_ = showSql(configuration, boundSql);
			saveMonitorSql(connection,sql_, startTime, costTime,time);
		//}
       return returnValue;
    }  
   
    @Async
	void saveMonitorSql(Connection connection, final String sql, final Date startTime,
						final Date endTime, final Long time) {

		String type = "4";
		String tempSql = sql.trim().toUpperCase();
		if (tempSql.startsWith("INSERT"))
			type = "1";
		else if (tempSql.startsWith("UPDATE"))
			type = "2";
		else if (tempSql.startsWith("DELETE")) {
			type = "3";
		}
		PreparedStatement preparedStatement = null;
		//通过connection建立一个countSql对应的PreparedStatement对象。
		try {
			preparedStatement = connection.prepareStatement("insert into t_sys_sql (id, description,"+
					"start_time, end_time,time_consum, type) values (?,?,?,?,?,?)");

			preparedStatement.setString(1,CodeUtil.getTimeVersion());
			preparedStatement.setString(2,getSql(CodeUtil.getTimeVersion(), sql, time));
			preparedStatement.setTimestamp(3, new java.sql.Timestamp(startTime.getTime()));
			preparedStatement.setTimestamp(4,new java.sql.Timestamp(endTime.getTime()));
			preparedStatement.setInt(5,time.intValue());
			preparedStatement.setString(6,type);
			preparedStatement.executeUpdate();
		} catch (SQLException e) {
			log.error("保存数据库执行语句时出现异常",e);
		} finally {
			try {
				if (preparedStatement != null){
					preparedStatement.close();
				}
			} catch (SQLException e) {
				log.error("保存数据库执行语句关闭流时出现异常",e);
			}
		}
	}

	public static String getSql(String sqlId, String sql, long time) {
		return "执行SQL(" +
				sqlId +
				"):" +
				sql +
				"【使用时间" +
				time +
				"ms】";
	}

	private static String getParameterValue(Object obj) {
		String value;
		if ((obj instanceof String)) {
			value = "'" + obj.toString() + "'";
		} else if ((obj instanceof Date)) {
			DateFormat formatter = DateFormat.getDateTimeInstance(2, 2,
					Locale.CHINA);
			value = "'" + formatter.format(new Date(0)) + "'";
		} else if (obj != null) {
			value = obj.toString();
		} else {
			value = "";
		}

		return value;
	}

	public static String showSql(Configuration configuration, BoundSql boundSql) {
		Object parameterObject = boundSql.getParameterObject();
		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
		String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
		if ((parameterMappings.size() > 0) && (parameterObject != null)) {
			TypeHandlerRegistry typeHandlerRegistry = configuration
					.getTypeHandlerRegistry();
			if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
				sql = sql.replaceFirst("\\?",
						getParameterValue(parameterObject));
			} else {
				MetaObject metaObject = configuration.newMetaObject(parameterObject);
				for (ParameterMapping parameterMapping : parameterMappings) {
					String propertyName = parameterMapping.getProperty();
					if (metaObject.hasGetter(propertyName)) {
						Object obj = metaObject.getValue(propertyName);
						sql = sql.replaceFirst("\\?", getParameterValue(obj));
					} else if (boundSql.hasAdditionalParameter(propertyName)) {
						Object obj = boundSql
								.getAdditionalParameter(propertyName);
						sql = sql.replaceFirst("\\?", getParameterValue(obj));
					}
				}
			}
		}
		return sql;
	}
	
   
    /** 
     * 拦截器对应的封装原始对象的方法 
     */  
    public Object plugin(Object target) {  
       return Plugin.wrap(target, this);  
    }  
   
    /** 
     * 设置注册拦截器时设定的属性 
     */  
    public void setProperties(Properties properties) {}
     
    /** 
     * 利用反射进行操作的一个工具类 
     * 
     */  
    private static class ReflectUtil {  
       /** 
        * 利用反射获取指定对象的指定属性 
        * @param obj 目标对象 
        * @param fieldName 目标属性 
        * @return 目标属性的值 
        */  
       public static Object getFieldValue(Object obj, String fieldName) {  
           Object result = null;  
           Field field = ReflectUtil.getField(obj, fieldName);  
           if (field != null) {  
              field.setAccessible(true);  
              try {  
                  result = field.get(obj);  
              } catch (IllegalArgumentException | IllegalAccessException e) {
                  e.printStackTrace();  
              }
		   }
           return result;  
       }  
        
       /** 
        * 利用反射获取指定对象里面的指定属性 
        * @param obj 目标对象 
        * @param fieldName 目标属性 
        * @return 目标字段 
        */  
       private static Field getField(Object obj, String fieldName) {  
           Field field = null; 
           try {
        	   for (Class<?> clazz=obj.getClass(); clazz != Object.class; clazz=clazz.getSuperclass()) {  
                   try {  
                       field = clazz.getDeclaredField(fieldName);  
                       break;  
                   } catch (NoSuchFieldException e) {  
                       //这里不用做处理，子类没有该字段可能对应的父类有，都没有就返回null。  
                   }  
                } 
		} catch (Exception e) {
			return null;
		}
           
           return field;  
       }  
   
    }  
   
}  